﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MonoSettlers
{
    /// <summary>
    /// A path node contains all necessary information to provide animation,
    /// planning and cooperation.
    /// </summary>
    internal class MovablePathNode
    {
        /// <summary>
        /// The corresponding key for the internal space-time map.
        /// </summary>
        public PathNodeKey Key { get; set; }
        /// <summary>
        /// A direction of animation, if any.
        /// </summary>
        public Direction? Direction { get; set; }
        /// <summary>
        /// A path node is idle, if it has no direction.
        /// </summary>
        public Boolean IsIdle { get { return !Direction.HasValue; } }
        /// <summary>
        /// The movable owning this path node.
        /// </summary>
        public Movable Movable { get; set; }

        public override string ToString()
        {
            return Key + "; Dir: " + Direction;
        }
    }

    /// <summary>
    /// The base class for all movables. 
    /// </summary>
    public class Movable : IPositionTracker, IMovable
    {
        /// <summary>
        /// The current path node. TODO: In future this should be a read-only property
        /// which efficiently determines its value instead of being set explicitly.
        /// </summary>
        internal LinkedListNode<MovablePathNode> CurrentNode { get; set; }
        /// <summary>
        /// A list of path nodes currently in the queue.
        /// </summary>
        internal LinkedList<MovablePathNode> Path { get; private set; }
        /// <summary>
        /// The movable type. Currently ignored, but later this will provide ways to,
        /// for example, letting soldiers walk over snow while settlers couldn't.
        /// </summary>
        internal int MovableType { get; set; }
        /// <summary>
        /// The time when the engine should issue a new path. This is usually the time
        /// of the last path node.
        /// </summary>
        internal long ReplanTime { get; set; }
        /// <summary>
        /// This instance will take <see cref="CycleSpeed"/> times the <see cref="MovableManager.CycleResolution"/>
        /// to pass one grid cell. Currently this value is readonly and hardcoded to one (due to lack of support
        /// of the path engine, since it would make much things unnecessary complicated which should be avoided
        /// as long as the path engine itself is not feature complete; and to be honest it is already complicated enough
        /// to understand cooperative path planning even without having to think of different speeds and sizes; to
        /// be precise in theory it is easy but we are using a highly optimized version that additionally has a discrete 
        /// space-time and is 100% deterministic).
        /// </summary>
        internal int CycleSpeed { get; private set; }
        /// <summary>
        /// This is the grid cell where this instance has started moving from.
        /// </summary>
        internal Point PathSource { get; set; }
        /// <summary>
        /// This is the grid cell where this instance is being moved to.
        /// </summary>
        internal Point PathTarget { get; set; }
        /// <summary>
        /// The movable manager this instance is attached to (or null).
        /// </summary>
        internal MovableManager Parent { get; set; }
        /// <summary>
        /// An optional handler being called when the movable has reached its target
        /// or is stopped.
        /// </summary>
        internal Procedure<bool> ResultHandler { get; set; }

        /// <summary>
        /// Is this instance currently carrying out a job?
        /// </summary>
        public Boolean HasJob { get { return ResultHandler != null; } }
        /// <summary>
        /// One of the direction properties. This is just for rendering.
        /// </summary>
        public Direction Direction { get; internal set; }
        /// <summary>
        /// If any, the resource currently carried (an eventually dropped) by this instance.
        /// </summary>
        public Resource? Carrying { get; set; }
        public CyclePoint Position { get; private set; }
        /// <summary>
        /// Is movable eligable for new jobs?
        /// </summary>
        public bool IsIdle { get { return !HasJob;/* && !UserControlable;*/ } }
        public bool UserControlable { get; private set; }

        /// <summary>
        /// Is raised by <see cref="MovableManager.AddOneCycle"/> whenever it changes the movable position.
        /// </summary>
        public event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
        /// <summary>
        /// Is raised by <see cref="MovableManager.AddOneCycle"/> whenever it changes the movable direction.
        /// </summary>
        public event DChangeHandler<IPositionTracker, Direction> OnDirectionChanged;
        /// <summary>
        /// Is raised whenever <see cref="Stop"/> is called.
        /// </summary>
        public event Procedure<IMovable> OnStop;

        public Movable(CyclePoint inInitialPosition, bool inUserControlable)
        {
            Position = inInitialPosition;
            Path = new LinkedList<MovablePathNode>();
            PathTarget = PathSource = inInitialPosition.ToPoint();
            MovableType = 0;
            CycleSpeed = 1;
            UserControlable = inUserControlable;
        }

        /// <summary>
        /// This should be the only point where a position is changed, except for the constructor.
        /// The vector is expected to be of length one. But there may be rare! exceptions.
        /// </summary>
        internal void AddCycleVector(Point inVector)
        {
            Position = Position.AddCycleVector(inVector);
        }

        /// <summary>
        /// Is raised by <see cref="MovableManager.AddOneCycle"/> whenever it changes the movable position.
        /// </summary>
        internal void RaisePositionChange(CyclePoint inOldValue)
        {
            if (OnPositionChanged != null)
                OnPositionChanged(this, inOldValue, Position);
        }

        /// <summary>
        /// Is raised by <see cref="MovableManager.AddOneCycle"/> whenever it changes the movable direction.
        /// </summary>
        internal void RaiseDirectionChange(Direction inOldValue)
        {
            if (OnDirectionChanged != null)
                OnDirectionChanged(this, inOldValue, Direction);
        }

        /// <summary>
        /// An object only is considered moving if its current path node got a direction, the only case in 
        /// which true is returned.
        /// </summary>
        public Boolean IsMoving
        {
            get {
                return CurrentNode.Value.Direction.HasValue;
            }
        }

        /// <summary>
        /// Forces path replanning in next aligned cycle. 
        /// </summary>
        internal void Invalidate()
        {
            ReplanTime = Parent.CurrentCycle;
        }

        /// <summary>
        /// Stops movable in next aligned cycle, causing it to drop resources being carried (if any),
        /// and becoming idle.
        /// </summary>
        public void Stop()
        {
            var handler = ResultHandler;
            PathTarget = PathSource = Position.ToPoint();

            if (handler != null)
            {
                ResultHandler = null;

                handler(false);
            }

            Invalidate();

            if (OnStop != null)
                OnStop(this);
        }

        public void Dispose()
        {
            Stop();
        }
    }
}
